package plgdb

import (
	"dgo/MyProj/PrjDBUtils"
	"dgo/MyProj/PrjHttpServ"
	"dgo/MyProj/PrjHttpUtils"
	db_utils "dgo/goutils/db-utils"
	"dgo/goutils/gohttp"
	"dgo/goutils/utils"
	"dgo/plg/plgbase"
	"dgo/plg/plgnet"
	"fmt"
	"gitee.com/ymofen/gobase/golog"
	"gitee.com/ymofen/supervalue"
	"net/http"
	"strings"
	"time"
)

type PlgDBBase struct {
	plgbase.PlgBase
	dbres_file    string
	dbres_url     string
	dbres_prefix  string
	api_dbhandler string

	// 没必要使用, 可以设定连接池使用一个连接
	Dbup_task_key string // up, del, add, exec用队列操作,

	// 查询数据最大打包数量, 默认3000
	DefaultMaxPackageRowCount int

	// 注册的uri
	api_uri_map          utils.GSyncMapGroup
	Default_datasourceid string
	Warnlst              *utils.SyncCycleList

	// 是否运行跨域
	Http_allow_cross_access bool
	TraceDebug              int

	OnRegisterDBHandlerApiFunc   func(api_dbhandler string)
	OnUnRegisterDBHandlerApiFunc func(api_dbhandler string)
	OnBeforeReloadConfFunc       func(conf *wrapper.SuperValue)
	OnReloadConfFunc             func(conf *wrapper.SuperValue)
	OnReleaseFunc                func()
	OnNewDAOHelperFunc           func(datasourceid string, repl_db string, args ...interface{}) *PrjDBUtils.DAOHelper

	OnInitialReqFunc func(req *PlgDBRequest)

	firstRunFlag   byte
	OnFirstRunFunc func()

	/***
	  uptype: 更新类型 DBCOMM_UPTYPE_APPEND, DBCOMM_UPTYPE_UPDATE
	  param: 更新参数
	*/
	OnAfterUpdateFunc func(param *PlgDBUpdateParam, row *wrapper.SuperValue, uptype int)

	/***
	  更新之前
	*/
	OnBeforeUpdateRequestFunc func(param *PlgDBUpdateParam)

	/**
	  查询之前
	*/
	OnBeforeQryRequestFunc func(param *PlgDBQryParam) bool

	/**
	  之前
	*/
	OnBeforeRequestFunc func(req *PlgDBRequest)
}

func NewPlgDBBase() *PlgDBBase {
	rval := &PlgDBBase{}
	return rval
}

func (this *PlgDBBase) GetApiPrefix() string {
	return this.api_dbhandler
}

func (this *PlgDBBase) NewDAOHelper(datasourceid string) *PrjDBUtils.DAOHelper {
	if len(datasourceid) == 0 {
		return PrjDBUtils.NewDAOHelper(this.Default_datasourceid)
	} else {
		return PrjDBUtils.NewDAOHelper(datasourceid)
	}
}

func (this *PlgDBBase) NewDAOHelperEx(datasourceid string, repl_db string) *PrjDBUtils.DAOHelper {
	if len(datasourceid) == 0 {
		return PrjDBUtils.NewDAOHelperEx(this.Default_datasourceid, repl_db)
	} else {
		return PrjDBUtils.NewDAOHelperEx(datasourceid, repl_db)
	}
}
func (this *PlgDBBase) DoNewDAOHelper(datasourceid string, repl_db string, args ...interface{}) *PrjDBUtils.DAOHelper {
	if this.OnNewDAOHelperFunc != nil {
		return this.OnNewDAOHelperFunc(datasourceid, repl_db, args...)
	} else {
		if len(repl_db) > 0 {
			return this.NewDAOHelperEx(datasourceid, repl_db)
		}
		return this.NewDAOHelper(datasourceid)
	}
}

func (this *PlgDBBase) UnRegisterAPI(key string) {
	uri := this.api_uri_map.Get(key, "").(string)
	if len(uri) > 0 {
		gohttp.UnHandle(uri)
	}
}

func (this *PlgDBBase) CheckRegisterApiURIEx(urisuffix string, fn func(req *PlgDBRequest)) {
	this.CheckRegisterApi(urisuffix, this.api_dbhandler+urisuffix, func(w http.ResponseWriter, r *http.Request) {
		req := this.NewPlgDBRequest(w, r)
		if this.OnInitialReqFunc != nil {
			this.OnInitialReqFunc(req)
		}
		fn(req)
	})
}

func (this *PlgDBBase) CheckRegisterApiURI(urisuffix string, fn http.HandlerFunc) {
	this.CheckRegisterApi(urisuffix, this.api_dbhandler+urisuffix, fn)
}

func (this *PlgDBBase) CheckRegisterApiEx(uri string, fn http.HandlerFunc) {
	this.CheckRegisterApi(uri, uri, fn)
}

func (this *PlgDBBase) CheckRegisterApi(key string, uri string, fn http.HandlerFunc) {
	uri0 := this.api_uri_map.Get(key, "").(string)
	if uri0 != uri {
		if len(uri0) > 0 {
			gohttp.UnHandle(uri)
		}
		this.api_uri_map.Set(key, uri)
		if len(uri) > 0 {
			gohttp.HandleFunc(uri, fn)
		}
	}
}

func (this *PlgDBBase) innerReloadApiPrefix(strv string) {
	if this.api_dbhandler != strv {
		if len(this.api_dbhandler) > 0 {
			gohttp.UnHandle(this.api_dbhandler)
			gohttp.UnHandle(this.api_dbhandler + "sys/res/reload")
			if this.OnUnRegisterDBHandlerApiFunc != nil {
				this.OnUnRegisterDBHandlerApiFunc(this.api_dbhandler)
			}
		}
		this.api_dbhandler = strv
		if len(this.api_dbhandler) > 0 {
			gohttp.HandleFunc(this.api_dbhandler+"sys/res/reload", this.OnHttpResReloadHandler)
			gohttp.HandleFunc(this.api_dbhandler, this.OnHttpDBHandler)

			if this.OnRegisterDBHandlerApiFunc != nil {
				this.OnRegisterDBHandlerApiFunc(this.api_dbhandler)
			}
		}
	}
}

/*
*

	  {
		  "id": "db-helper",
		  "type": "db.base",
		  "conf": {
	        "dbblock-max":40,
			"datasource":[
			  {"id":"kpl-vrs", "drivername":"mysql", "connstring":"admin:WXvrs@admin!@#1@tcp(119.96.188.188:3306)/gnss-vrs?charset=utf8&parseTime=true&loc=Local"},
			  {"id":"dcloud", "drivername":"mysql", "connstring":"admin:WXvrs@admin!@#1@tcp(119.96.188.188:3306)/dcloud?charset=utf8&parseTime=true&loc=Local"}
			],
			"http-allow-cross-access": true,  // 允许跨域访问
	        "trace-debug":1,
			"db-res-delay-secs": 1,   // 延迟加载
			"db-res-file":"db.res",
			"db-res-prefix":"xxx",    // 所有id之前都加 xxx
			"db-res-url":"http://127.0.0.1:3000/plug/do?id=dcloud&accesstoken=kpl&action=pack&projectid=kpl-cloud",
	        "api-dbhandler":"/api/web/",     // db处理api [RES]/list, update, del,
	        "default-datasourceid":"dcloud",
			"dbres":[
			  {
				"id":"vrs-batch", "conf":{
				   "$sql":{
					 "sql":"update caster_ntripsvr set fstate=$state$ where fsubnet='$subnet$'"
					}
				 }
			  }
			]
	  }
*/
func (this *PlgDBBase) ReloadConf(conf *wrapper.SuperValue) (err error) {
	if this.Warnlst == nil {
		this.Warnlst = utils.NewSyncCycleList()
		this.Warnlst.ConfigMaxSize(1024)
	}

	if this.firstRunFlag == 0 && this.OnFirstRunFunc != nil {
		this.firstRunFlag = 1
		this.OnFirstRunFunc()
	}

	if this.OnBeforeReloadConfFunc != nil {
		this.OnBeforeReloadConfFunc(conf)
	}

	PrjDBUtils.LoadDataSourceConfig(conf.ValueByName("datasource"))

	conf.CheckExistsStrFunc("default-datasource", func(strv string) {
		this.Default_datasourceid = strv
	})

	conf.CheckExistsStrFunc("default-datasourceid", func(strv string) {
		this.Default_datasourceid = strv
	})

	conf.CheckExistsIntFunc("dbblock-max", func(val int64) {
		db_utils.ConfigDBConnBlock(int32(val))
	})
	conf.CheckExistsIntFunc("trace-debug", func(val int64) {
		this.TraceDebug = int(val)
	})

	conf.CheckExistsStrFunc("api-dbhandler", func(strv string) {
		this.innerReloadApiPrefix(strv)
	})

	conf.CheckExistsStrFunc("api-prefix", func(strv string) {
		this.innerReloadApiPrefix(strv)
	})

	conf.CheckStrFunc("dbup_task_key", func(strv string) {
		this.Dbup_task_key = strv
	})

	dbres_file_flag := 0
	conf.CheckStrFunc("db-res-file", func(strv string) {
		if this.dbres_file != strv {
			PrjHttpServ.DBResCleanEx(0, this.PlugId)
			this.dbres_file = strv
			dbres_file_flag = 1
		}
	})

	conf.CheckStrFunc("db-res-url", func(strv string) {
		this.dbres_url = strv
	})

	conf.CheckStrFunc("db-res-prefix", func(strv string) {
		if this.dbres_prefix != strv {
			PrjHttpServ.DBResCleanEx(0, this.PlugId)
			this.dbres_prefix = strv
			dbres_file_flag = 1
		}
	})

	conf.CheckExistsStrFunc("http-allow-cross-access", func(strv string) {
		this.Http_allow_cross_access = utils.TryStrToBool(strv, false)
	})

	if dbres_file_flag == 1 {
		this.ReloadDBRes()
	}

	PrjHttpServ.RegisterDBResList(this.dbres_prefix, conf.ValueByName("dbres"), 1, this.PlugId)

	if this.OnReloadConfFunc != nil {
		this.OnReloadConfFunc(conf)
	}

	if this.DefaultMaxPackageRowCount <= 0 {
		this.DefaultMaxPackageRowCount = 3000
	}
	return
}

func (this *PlgDBBase) RegisterDBResItem(resid string, conf *wrapper.SuperValue) {
	if conf == nil {
		golog.Warnf("[%s]资源配置非法", resid)
		return
	}
	PrjHttpServ.RegisterDBResEx(fmt.Sprintf("%s%s", this.dbres_prefix, resid), conf, 1, this.PlugId)
}

func (this *PlgDBBase) reloadFromUrl() (int, error) {
	err := PrjHttpServ.DownResFromServ(this.dbres_url, this.dbres_file, "")
	if err != nil {
		return 0, err
	} else {
		return PrjHttpServ.RegisterDBResFromFile(this.dbres_prefix, this.dbres_file, 0, this.PlugId)
	}
}

func (this *PlgDBBase) PushWarnlist(s string, args ...interface{}) {
	msg := fmt.Sprintf(s, args...)
	this.Warnlst.Push(fmt.Sprintf("[%s]%s", utils.NowString(), msg))
}

func (this *PlgDBBase) innerRelease() {
	if len(this.api_dbhandler) > 0 {
		gohttp.UnHandle(this.api_dbhandler)
		gohttp.UnHandle(this.api_dbhandler + "sys/res/reload")
		if this.OnUnRegisterDBHandlerApiFunc != nil {
			this.OnUnRegisterDBHandlerApiFunc(this.api_dbhandler)
		}
	}

	this.api_uri_map.Range(func(key, value interface{}) bool {
		gohttp.UnHandle(value.(string))
		return true
	})

	PrjHttpServ.DBResClean(this.PlugId)
}

func (this *PlgDBBase) GetDBResConf(resid string) *wrapper.SuperValue {
	if len(this.dbres_prefix) == 0 {
		return PrjHttpServ.GetDBRes(resid)
	}
	return PrjHttpServ.GetDBRes(fmt.Sprintf("%s%s", this.dbres_prefix, resid))
}

func (this *PlgDBBase) ReloadDBRes() (int, error) {
	n, err := PrjHttpServ.RegisterDBResFromFile(this.dbres_prefix, this.dbres_file, 0, this.PlugId)
	if err != nil || n == 0 {
		time.AfterFunc(time.Second, func() { // 延迟加载
			n, err := this.reloadFromUrl()
			if err != nil {
				golog.Warnf("加载资源异常:%s", err.Error())
				return
			}
			golog.Infof("加载资源成功[%d]", n)
		})
		return 0, nil
	}
	return n, err
}

func (this *PlgDBBase) OnHttpStatusFillStatus99(json *wrapper.SuperValue) {
	json.SetKeyValues("allow-cross", this.Http_allow_cross_access,
		"default-datasource", this.Default_datasourceid,
		"api-prefix", this.api_dbhandler,
	)
	if len(this.Dbup_task_key) > 0 {
		json.SetKeyValue(this.Dbup_task_key, utils.DefaultWorkers().GroupSimpleStatus(this.Dbup_task_key))
	}
}

func (this *PlgDBBase) OnHttpStatusResponseStatusTable(rec *plgnet.PlgHttpRec, fillfn func(json *wrapper.SuperValue)) {
	json := wrapper.NewSVObject()
	fillfn(json)
	PrjHttpUtils.JSON2TableRec(rec.RespW, "", json.AsJSONString(false))
}

func (this *PlgDBBase) OnHttpStatus(rec *plgnet.PlgHttpRec) (rval interface{}, errcode int, err error) {
	itype := rec.QryAsInt("type", 0)
	if itype == 99 {
		this.OnHttpStatusResponseStatusTable(rec, func(json *wrapper.SuperValue) {
			this.OnHttpStatusFillStatus99(json)
		})
	} else if itype == 1 {
		if db_utils.DBConnBlock == nil {
			rec.RespW.Write([]byte("DBConnBlock is nil"))
		} else {
			rec.RespW.Write([]byte(db_utils.DBConnBlock.String()))
		}
	} else if itype == 2 {
		rec.ResponseSyncListN(this.Warnlst, rec.QryAsString("searchval", ""), rec.QryAsInt("n", 0))

	} else {
		var sb utils.BytesBuilder
		PrjHttpUtils.AppendIndexBody(&sb, "status", "status?type=99")
		PrjHttpUtils.AppendIndexBody(&sb, "dbblock-status", "status?type=1")
		PrjHttpUtils.AppendIndexBody(&sb, "warnlist", "status?type=2&n=100&searchval=")
		PrjHttpUtils.ResponseBodyHtml(rec.RespW, sb.String())
	}
	return
}

/*
*

	{
	   "datasource":"main",
	   "exec_resid":"xx",
	   "args":{
	       "$subnet$":"HGNET",
	       "$state$":0
	    }
	}
*/
func (this *PlgDBBase) OnHttpDo(rec *plgnet.PlgHttpRec) (rval interface{}, errcode int, err error) {
	if this.Http_allow_cross_access {
		PrjHttpUtils.AllowCrossAccessSet(rec.RespW)
	}

	datasouceid := rec.QryAsString("datasource", "")
	if len(datasouceid) == 0 {
		return nil, 503, fmt.Errorf("请指定datasource")
	}
	exec_resid := rec.QryAsString("exec_resid", "")
	if len(exec_resid) > 0 {
		args := rec.Req.ForceByPath("args")
		rec.RangeURLParam(func(k, v string) bool {
			strs := strings.SplitN(k, ".", 2)
			if len(strs) == 2 && strs[0] == "args" {
				args.SetKeyString(strs[1], v)
			}
			return true
		})

		sql := PrjHttpServ.GetDBResSQL(exec_resid, "$sql", args)
		if len(sql) == 0 {
			return nil, 503, fmt.Errorf("%s对应的$sql没有进行配置", exec_resid)
		}
		n, errsql, err1 := PrjDBUtils.ExecuteBatchSQL(datasouceid, sql)
		if err1 != nil {
			rec.ResponseJSONValues("code", -1, "errsql", errsql, "msg", err1.Error())
			return
		} else {
			rec.ResponseJSONValues("code", 0, "upcnt", n)
		}
	}

	rec.ResponseJSONValues("code", -1, "msg", "nothing to do...")
	return
}

func (this *PlgDBBase) OnObjectNotify(sender interface{}, notifytype int, data interface{}) (rval interface{}, err error) {
	if notifytype == utils.NOTIFY_TYPE_SETCONF {
		if conf, ok := data.(*wrapper.SuperValue); ok {
			err = this.ReloadConf(conf)
		} else {
			err = fmt.Errorf("非法的JSON配置")
		}
	} else if notifytype == utils.NOTIFY_TYPE_RELEASE {
		if this.OnReleaseFunc != nil {
			this.OnReleaseFunc()
		}
		this.innerRelease()

	}
	return
}

func CreatePlgDBBaseFunc(args ...interface{}) (rval interface{}, err error) {
	obj := NewPlgDBBase()
	if len(args) > 0 {
		if str, ok := args[0].(string); ok {
			obj.PlugId = str
		}
	}
	rval = obj
	return
}
